package game;

import java.io.IOException;

/**
 * Game is resource which is shared between 2 players who play game together. It
 * synchronizes the actions of the 2 players.
 * <p>
 * In the threads' terminology <Game> is a monitor which each of the the 2
 * players(threads) try to access.
 * 
 * @author Tsvetan Vasilev
 * 
 */
public class Game {
	// 3 references to firstPlayer, seccondPlayer and Winner objects.
	private Player fPlayer;
	private Player sPlayer;
	private Player winner;

	// the 2 secret numbers chosen by firstPlayer and secondPlayer
	// Each player should guess the opponent's secret number
	private String fSecretNumber;
	private String sSecretNumber;

	// a boolean which holds the players' turn.
	// If true - second playre's turn. If false - first playre's turn
	private boolean isSecondPlayerTurn;

	// it is a marker used for printing a message( about guess number) on first
	// iteration
	private boolean firstIterationPrintMarker = true;

	// --- Constructors,Getters,Setters ----------------------------------------
	public Game(Player fPlayer, Player sPlayer) {
		this.fPlayer = fPlayer;
		this.sPlayer = sPlayer;
	}

	// --- Methods -------------------------------------------------------------
	/**
	 * Checks if the player is a first player in the game.
	 */
	public boolean isFirstPlayer(Player player) {
		return fPlayer == player;
	}

	public synchronized boolean isSecondPlayerTurn() {
		return isSecondPlayerTurn;
	}

	public synchronized Player getOpponent(Player player) {
		return (player == fPlayer) ? sPlayer : fPlayer;
	}

	/**
	 * Sends message to the player when the game is finished.
	 */
	public synchronized void announceWinner(Player player) {
		if (winner == player) {
			player.getPlayerIOManager().announceWinner();
		} else {
			player.getPlayerIOManager().announceWinner(getOpponent(player));
		}
	}

	/**
	 * Delegate the player's choice of secret number.
	 * 
	 * @param player
	 * @param secretNumber
	 * @throws InterruptedException
	 */
	public synchronized void chooseSecretNumber(Player player,
			String secretNumber) throws InterruptedException {

		if (player == fPlayer) {
			fSecretNumber = secretNumber;

			while (sSecretNumber == null) {
				wait();
			}
			notifyAll();
		} else {
			sSecretNumber = secretNumber;

			while (fSecretNumber == null) {
				wait();
			}
			notifyAll();
		}
	}

	/**
	 * Executes the player's actions when he/she is on turn.
	 * 
	 * @param player
	 * @throws InterruptedException
	 * @throws IOException
	 */
	public synchronized void playTurn(Player player)
			throws InterruptedException, IOException {
		while (!isOnTurn(player)) {
			wait();
		}
		if (isOver()) {
			return;
		}

		// it is firstPlayer's turn => so ACTION

		// guess opponent number
		String opponentNumber = player.getPlayerIOManager()
				.guessOpponentNumber();

		guess(player, opponentNumber);
		changeTurn();

		notifyAll();
	}

	/**
	 * Checks the guess of player and prints appropriate result messages
	 * 
	 * @param player
	 * @param opponentGuessNmbr
	 */
	public synchronized void guess(Player player, String opponentGuessNmbr) {
		if (player == fPlayer) {
			executeGuess(fPlayer, sPlayer, sSecretNumber, opponentGuessNmbr);
		} else {
			executeGuess(sPlayer, fPlayer, fSecretNumber, opponentGuessNmbr);
		}
	}

	public synchronized boolean isOver() {
		return winner != null;
	}

	/**
	 * Notify the 2 players when the game finishes because some of them might be
	 * waiting.
	 */
	public synchronized void setPlayersFree() {
		notifyAll();
	}

	// --- Auxiliary Methods ---------------------------------------------------

	private synchronized boolean isOnTurn(Player player) {
		if (player == fPlayer) {
			return isSecondPlayerTurn == false;
		} else {
			return isSecondPlayerTurn == true;
		}
	}

	private synchronized void changeTurn() {
		isSecondPlayerTurn = !isSecondPlayerTurn;
	}

	private synchronized void executeGuess(Player guesser, Player opponent,
			String opponentSecretNumber, String guesserGuess) {
		String bullsNcows = calculateBullsAndCows(opponentSecretNumber,
				guesserGuess);

		if ("4b0c".equals(bullsNcows)) {
			winner = guesser;
		}

		if (firstIterationPrintMarker == true && guesser == fPlayer) {
			// first player
			opponent.getPlayerIOManager().passPaddingFirstIteration();
			firstIterationPrintMarker = false;
		}
		guesser.getPlayerIOManager().notifyInterimGameState(guesserGuess,
				bullsNcows);
		opponent.getPlayerIOManager().notifyForOpponentMove(guesser.getName(),
				guesserGuess, bullsNcows);
	}

	private synchronized String calculateBullsAndCows(String secretNumber,
			String guessNumber) {
		byte bulls = 0;
		byte cows = 0;
		int[] realNumberCiphers = getCiphersInNumber(secretNumber);
		int[] guessNumberCiphers = getCiphersInNumber(guessNumber);
		int numberLength = secretNumber.length();
		for (int i = 0; i < numberLength; i++) {
			if (guessNumberCiphers[i] == realNumberCiphers[i]) {
				bulls++;
			} else {
				for (int j = 0; j < numberLength; j++) {
					if (guessNumberCiphers[i] == realNumberCiphers[j]) {
						cows++;
						break;
					}
				}
			}
		}
		return bulls + "b" + cows + "c";
	}

	private static int[] getCiphersInNumber(String stringNumber) {
		int number = Integer.parseInt(stringNumber);
		int numberLength = stringNumber.length();
		int[] ciphers = new int[numberLength];
		for (int i = numberLength; i >= 1; i--) {
			ciphers[numberLength - i] = (number / (int) Math.pow(10, i - 1)) % 10;
		}
		return ciphers;
	}
}
